/*!
\copyright  Copyright (c) 2025 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\file       fast_pair_l2cap.c
\brief      Implementation of L2CAP transport functionality for Fast Pair Service
*/

#include <bdaddr.h>
#include <source.h>
#include <sink.h>
#include <l2cap_manager.h>
#include "fast_pair.h"
#include "fast_pair_l2cap.h"

typedef struct
{
    bdaddr bd_addr;
    Source source;
    Sink sink;
} connection_data_t;

typedef struct
{
    l2cap_manager_instance_id l2cap_instance_id;
    uint32 connection_id;
    uint16 psm;
    void* ctx;
    bool in_use;
    fast_pair_le_l2cap_message_stream_state_t message_stream_state;
    connection_data_t connection;
} channel_instance_t;

static channel_instance_t channel_instance[FASTPAIR_LE_L2CAP_CONNECTIONS_MAX];

/*! \brief Generate the random number for the PSM which will be read by GATT Characteristics */
static uint16 fastpairlel2cap_GetRandomPsmValue(void)
{
    /* Generate a random number*/
    uint16 random_psm_value = UtilRandom();

    /* generating the new PSM value number which will be in range of 0x80 to 0xFF as 
    utilrandom gives random number between  0 to 65535*/
    random_psm_value = (random_psm_value % (FASTPAIR_LE_L2CAP_CREDIT_PSM_VALUE_MIN + 1) +
                     (FASTPAIR_LE_L2CAP_CREDIT_PSM_VALUE_MAX - FASTPAIR_LE_L2CAP_CREDIT_PSM_VALUE_MIN) );

    DEBUG_LOG("fastpairlel2cap_GetRandomPsmValue PSM Value: 0x%x", random_psm_value);

    return random_psm_value;
}

/*! \brief Create an instance for LE L2CAP Connection */
static channel_instance_t * fastpairlel2cap_CreateInstance(uint32 connection_id)
{
    channel_instance_t * created_instance = NULL;

    for(uint8 i=0 ; i < FASTPAIR_LE_L2CAP_CONNECTIONS_MAX; i++)
    {
        if ( channel_instance[i].connection_id == connection_id)
        {
            created_instance = &channel_instance[i];
            break;
        }

        if (channel_instance[i].connection_id != connection_id && channel_instance[i].psm == L2CAP_MANAGER_PSM_INVALID)
        {
            channel_instance[i].connection_id = connection_id;
            /* A function to generate the L2CAP PSM in predefined range */
            channel_instance[i].psm = fastpairlel2cap_GetRandomPsmValue();

            if(FASTPAIR_LE_L2CAP_CREDIT_IS_VALID_PSM(channel_instance[i].psm))
            {
                created_instance = &channel_instance[i];
                DEBUG_LOG_DEBUG("fastpairlel2cap_CreateInstance PSM Value: 0x%x", channel_instance[i].psm);
                break;
            }
        }
    }

    DEBUG_LOG("fastpairlel2cap_CreateInstance connection_id : 0x%x instance %p",connection_id, created_instance);
    return created_instance;
}

/*! \brief Get an instance for LE L2CAP Connection */
static channel_instance_t * fastpairlel2cap_GetFreeInstance(uint16 psm)
{
    channel_instance_t * found_instance = NULL;

    for(uint8 i=0 ; i < FASTPAIR_LE_L2CAP_CONNECTIONS_MAX; i++)
    {
        if(channel_instance[i].psm == psm && !channel_instance[i].in_use)
        {
            found_instance = &channel_instance[i];
            break;
        }
    }
    DEBUG_LOG_DEBUG("fastpairlel2cap_GetFreeInstance PSM Value: 0x%x instance %p",psm, found_instance);
    return found_instance;
}

/*! \brief Set the state for the LE L2CAP Message Stream PSM which will be read by the GATT Characteristics  */
static void fastpairlel2cap_SetMessageStreamState(channel_instance_t * instance,fast_pair_le_l2cap_message_stream_state_t message_stream_state)
{
    DEBUG_LOG_DEBUG("fastpairlel2cap_SetMessageStreamState : instance %p PSM 0x%x enum:fast_pair_le_l2cap_message_stream_state_t:%d", instance, instance->psm, message_stream_state);
    instance->message_stream_state = message_stream_state;
}

static inline channel_instance_t * fastpairlel2cap_GetInstanceFromContext(void *context)
{
    return (channel_instance_t *)context;
}

static inline channel_instance_t * fastpairlel2cap_GetInstanceFromConnectionId(uint32 connection_id)
{
    channel_instance_t * found_instance = NULL;

    DEBUG_LOG_DEBUG("fastpairlel2cap_GetInstanceFromConnectionId received connection_id 0x%x", connection_id);

    for(uint8 i=0 ; i < FASTPAIR_LE_L2CAP_CONNECTIONS_MAX; i++)
    {
        if (channel_instance[i].connection_id == connection_id)
        {
            found_instance = &channel_instance[i];
            break;
        }
    }
    return found_instance;
}

static bool fastpairlel2cap_IsConnected(channel_instance_t * instance)
{
    return (instance->in_use  && !BdaddrIsZero(&instance->connection.bd_addr));
}

static void fastpairlel2cap_RegisteredInd(l2cap_manager_status_t status) 
{
    DEBUG_LOG_DEBUG("fastpairlel2cap_RegisteredInd status enum:l2cap_manager_status_t:%d", status);

    if (status != l2cap_manager_status_success)
    {
        DEBUG_LOG_WARN("fastpairlel2cap_RegisteredInd: ERROR! Failed to register L2CAP PSM!");
    }
    else
    {
        DEBUG_LOG_DEBUG("fastpairlel2cap_RegisteredInd: OK");
    }
}

static l2cap_manager_status_t fastpairlel2cap_GetL2capLinkConfig(const tp_bdaddr *tpaddr, l2cap_manager_l2cap_link_config_t *config)
{
    config->conftab = NULL;
    config->conftab_length = 0;
    config->security_level = 0;

    DEBUG_LOG_DEBUG("fastpairlel2cap_GetL2capLinkConfig  addr: %04x-%02x-%06x type:0x%X transport:0x%X",
                      tpaddr->taddr.addr.nap, 
                      tpaddr->taddr.addr.uap, 
                      tpaddr->taddr.addr.lap, 
                      tpaddr->taddr.type,
                      tpaddr->transport);

    return l2cap_manager_status_success;
}

static l2cap_manager_status_t fastpairlel2cap_GetL2capLeLinkConfig(const tp_bdaddr *tpaddr, l2cap_manager_le_l2cap_link_config_t *config)
{
    config->security_level = SEC_LE_NONE;
    config->local_mtu = FASTPAIR_LE_L2CAP_CREDIT_MAX_MTU; 
    config->initial_credit = FASTPAIR_LE_L2CAP_CREDIT_INITIAL_CREDITS;
    config->conn_flags = L2CA_CONFLAG_ENUM_MAX;

    DEBUG_LOG_DEBUG("fastpairlel2cap_GetL2capLeLinkConfig addr: %04x-%02x-%06x type:0x%X transport:0x%X",
                      tpaddr->taddr.addr.nap, 
                      tpaddr->taddr.addr.uap, 
                      tpaddr->taddr.addr.lap, 
                      tpaddr->taddr.type,
                      tpaddr->transport);

    return l2cap_manager_status_success;
}

/*! Check if the incoming/outgoing LE L2CAP  connection is allowed or not.
 * \return TRUE, if any more LE L2CAP connections are allowed. FALSE otherwise
 */
static bool fastpairlel2cap_IsConnectionAllowed(void)
{
    uint8 no_of_active_connections = 0;

    for(uint8 i = 0; i < FASTPAIR_LE_L2CAP_CONNECTIONS_MAX; i++)
    {
        if(fastpairlel2cap_IsConnected(&channel_instance[i]))
        {
            no_of_active_connections++;
        }
    }

    DEBUG_LOG_DEBUG("fastpairlel2cap_IsConnectionAllowed no_of_active_connections:%d", no_of_active_connections);

    return (no_of_active_connections < FASTPAIR_LE_L2CAP_CONNECTIONS_MAX);
}

static l2cap_manager_status_t fastpairlel2cap_RespondConnectInd(const l2cap_manager_connect_ind_t *ind, l2cap_manager_connect_rsp_t *rsp, void **context)
{
    channel_instance_t * targeted_instance = fastpairlel2cap_GetFreeInstance(ind->local_psm);
    DEBUG_LOG("fastpairlel2cap_RespondConnectInd Remote addr: %04x-%02x-%06x type:0x%X transport:0x%X psm:0x%x remote psm:0x%x identifier:0x%x con id:0x%x",
                    ind->tpaddr.taddr.addr.nap,
                    ind->tpaddr.taddr.addr.uap,
                    ind->tpaddr.taddr.addr.lap,
                    ind->tpaddr.taddr.type,
                    ind->tpaddr.transport,
                    ind->local_psm,
                    ind->remote_psm,
                    ind->identifier,
                    ind->connection_id);
    
    bool accept_connection = FALSE;

    if(   fastpairlel2cap_IsConnectionAllowed() 
       && targeted_instance 
       && ind->tpaddr.transport == TRANSPORT_BLE_ACL)
    {
        DEBUG_LOG("fastpairlel2cap_RespondConnectInd accepting");
        accept_connection = TRUE;
        targeted_instance->in_use = TRUE;
        targeted_instance->l2cap_instance_id = ind->connection_id;
        targeted_instance->connection.bd_addr = ind->tpaddr.taddr.addr;
    }
    else
    {
        DEBUG_LOG("fastpairlel2cap_RespondConnectInd rejecting");
    }

    *context = targeted_instance;
    rsp->response = accept_connection;
    rsp->conftab_length = 0;
    rsp->conftab = NULL;

    return (accept_connection ? l2cap_manager_status_success : l2cap_manager_status_failure);
}


static l2cap_manager_status_t fastpairlel2cap_HandleConnectCfm(const l2cap_manager_connect_cfm_t *cfm, void *context)
{
    channel_instance_t * this_instance = fastpairlel2cap_GetInstanceFromContext(context);
    DEBUG_LOG("fastpairlel2cap_HandleConnectCfm instance:%p status:0x%x local_psm:0x%x remote_psm:0x%x", this_instance, cfm->status, cfm->local_psm, cfm->remote_psm);
    this_instance->connection.sink = cfm->sink;
    this_instance->connection.source = StreamSourceFromSink(cfm->sink);
    this_instance->l2cap_instance_id = cfm->connection_id;
    this_instance->connection.bd_addr = cfm->tpaddr.taddr.addr;

    return l2cap_manager_status_success;
}

static l2cap_manager_status_t fastpairlel2cap_RespondDisconnectInd(const l2cap_manager_disconnect_ind_t *ind, void *context)
{
    channel_instance_t * this_instance = fastpairlel2cap_GetInstanceFromContext(context);
    DEBUG_LOG("fastpairlel2cap_RespondDisconnectInd instance:%p status:0x%x", this_instance, ind->status);
    this_instance->in_use = FALSE;
    memset(&this_instance->connection, 0, sizeof(this_instance->connection));

    return l2cap_manager_status_success;
}

static l2cap_manager_status_t fastpairlel2cap_HandleDisconnectCfm(const l2cap_manager_disconnect_cfm_t * cfm, void *context)
{
    channel_instance_t * this_instance = fastpairlel2cap_GetInstanceFromContext(context);
    DEBUG_LOG("fastpairlel2cap_HandleDisconnectCfm instance:%p status:0x%x", this_instance, cfm->status);
    this_instance->in_use = FALSE;
    memset(&this_instance->connection, 0, sizeof(this_instance->connection));

    return l2cap_manager_status_success;
}

static l2cap_manager_status_t fastpairlel2cap_ProcessMoreData(const l2cap_manager_message_more_data_t * more_data, void *context)
{
    channel_instance_t * this_instance = fastpairlel2cap_GetInstanceFromContext(context);
    DEBUG_LOG("fastpairlel2cap_ProcessMoreData instance:%p connection_id:0x%X", this_instance, more_data->connection_id);
    if (this_instance->connection.source != more_data->source)
    {
        DEBUG_LOG_ERROR("fastpairlel2cap_ProcessMoreData unmatched source expected:0x%04X actual:0x%04X", this_instance->connection.source, more_data->source);
        Panic();
    }
    const uint8 *source_map = SourceMap(this_instance->connection.source);
    uint16 data_length = SourceBoundary(this_instance->connection.source);
    uint8 * data = PanicUnlessMalloc(data_length);
    memmove(data, source_map, data_length);

    DEBUG_LOG("fastpairlel2cap_ProcessMoreData Data Received: %d bytes", data_length);

    SourceDrop(this_instance->connection.source, data_length);
    free(data);

    return l2cap_manager_status_success;
}

static l2cap_manager_status_t fastpairlel2cap_ProcessMoreSpace(l2cap_manager_message_more_space_t * more_space, void *context)
{
    channel_instance_t * this_instance = fastpairlel2cap_GetInstanceFromContext(context);
    DEBUG_LOG("fastpairlel2cap_ProcessMoreSpace instance:%p connection_id:0x%X",this_instance, more_space->connection_id);
    if (this_instance->connection.sink != more_space->sink)
    {
        DEBUG_LOG_ERROR("fastpairlel2cap_ProcessMoreSpace unmatched sink expected:0x%04X actual:0x%04X", this_instance->connection.sink, more_space->sink);
        Panic();
    }
    return l2cap_manager_status_success;
}

static l2cap_manager_functions_t l2cap_manager_functions = 
{
    .registered_ind = fastpairlel2cap_RegisteredInd,
    .get_l2cap_link_config = fastpairlel2cap_GetL2capLinkConfig,
    .respond_connect_ind = fastpairlel2cap_RespondConnectInd,
    .handle_connect_cfm = fastpairlel2cap_HandleConnectCfm,
    .respond_disconnect_ind = fastpairlel2cap_RespondDisconnectInd,
    .handle_disconnect_cfm = fastpairlel2cap_HandleDisconnectCfm,
    .process_more_data = fastpairlel2cap_ProcessMoreData,
    .process_more_space = fastpairlel2cap_ProcessMoreSpace,
    .get_l2cap_le_link_config = fastpairlel2cap_GetL2capLeLinkConfig,
};

bool FastpairLeL2cap_Create(uint32 connection_id)
{
    channel_instance_t * this_instance = fastpairlel2cap_CreateInstance(connection_id);
    bool created = FALSE;

    if (this_instance != NULL )
    {
        if(L2capManager_RegisterLe(this_instance->psm, &l2cap_manager_functions, &this_instance->l2cap_instance_id) == l2cap_manager_status_success)
        {
            fastpairlel2cap_SetMessageStreamState(this_instance, fast_pair_le_l2cap_message_stream_state_ready_to_connect);
            created = TRUE;
            DEBUG_LOG("FastpairLeL2cap_Create : instance %p PSM 0x%x instance_id 0x%x created %d", this_instance, this_instance->psm, this_instance->l2cap_instance_id, created);
        }
        else
        {
             DEBUG_LOG_WARN("FastpairLeL2cap_Create : Failed to Register the PSM");
        }
    }

    return created;
}

uint16 FastPairLeL2Cap_GetMessageStreamPsm(uint32 connection_id)
{
    uint16 psm_value = L2CAP_MANAGER_PSM_INVALID;

    channel_instance_t * found_instance = fastpairlel2cap_GetInstanceFromConnectionId(connection_id);
    if (found_instance)
    {
        psm_value = found_instance->psm;
        DEBUG_LOG_DEBUG("FastPairLeL2Cap_GetMessageStreamPsm : instance %p PSM 0x%x connection_id 0x%x", found_instance, psm_value, connection_id);
    }
    else
    {
        DEBUG_LOG_DEBUG("FastPairLeL2Cap_GetMessageStreamPsm couldn't find the instance for CID ");
    }

    return psm_value;
}

fast_pair_le_l2cap_message_stream_state_t FastPairLeL2Cap_GetMessageStreamState(uint32 connection_id)
{
    fast_pair_le_l2cap_message_stream_state_t message_stream_state = fast_pair_le_l2cap_message_stream_state_unavailable;

    for(uint8 i=0 ; i < FASTPAIR_LE_L2CAP_CONNECTIONS_MAX; i++)
    {
        if(FASTPAIR_LE_L2CAP_CREDIT_IS_VALID_PSM(channel_instance[i].psm))
        {
            message_stream_state = channel_instance[i].message_stream_state;
            break;
        }
    }

    DEBUG_LOG_DEBUG("FastPairLeL2Cap_GetMessageStreamState cid 0x%x Message Stream State enum:fast_pair_le_l2cap_message_stream_state_t:%d",connection_id,message_stream_state);

    return message_stream_state;
}

void FastPairLeL2cap_Init(void)
{
    /* initialise the channel instances. */
    for(uint8 i=0; i < FASTPAIR_LE_L2CAP_CONNECTIONS_MAX; i++)
    {
        memset(&channel_instance[i], 0, sizeof(channel_instance_t));
        fastpairlel2cap_SetMessageStreamState(&channel_instance[i], fast_pair_le_l2cap_message_stream_state_unavailable);
    }
}